/**
 * File:   command_audit.c
 * Author: AWTK Develop Team
 * Brief:  command_audit
 *
 * Copyright (c) 2023 - 2023  Guangzhou ZHIYUAN Electronics Co.,Ltd.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * License file for more details.
 *
 */

/**
 * History:
 * ================================================================
 * 2023-12-18 Li XianJing <xianjimli@hotmail.com> created
 *
 */

#include "mvvm/mvvm.h"
#include "tkc/date_time.h"
#include "command_audit.h"
#include "csv_view_model.h"
#include "csv/csv_file_object.h"

/*默认参数*/
/*默认用|分隔，因为|比较少用，如果使用','分隔，args则需要用英文引起来*/
#define COMMAND_AUDIT_FEIDLS_SEPERATOR '|'

/*最大记录数*/
#define COMMAND_AUDIT_MAX_ROWS 100000

typedef struct _command_audit_filter_ctx {
  int64_t start_date_time;
  int64_t end_date_time;
  const char* username;
  const char* command;

  uint32_t time_index;
  uint32_t username_index;
  uint32_t command_index;
  uint32_t args_index;
} command_audit_filter_ctx_t;

static tk_object_t* s_default_model = NULL;
static view_model_t* s_command_audit_vm = NULL;
static command_audit_filter_ctx_t s_command_audit_filter_ctx;

static ret_t command_audit_filter_info_init(command_audit_filter_ctx_t* info, tk_object_t* args) {
  date_time_t dt;
  return_value_if_fail(info != NULL && args != NULL, RET_BAD_PARAMS);

  const char* str_start_date = tk_object_get_prop_str(args, COMMAND_AUDIT_PROP_QUERY_START_DATE);
  const char* str_start_time = tk_object_get_prop_str(args, COMMAND_AUDIT_PROP_QUERY_START_TIME);
  const char* str_end_date = tk_object_get_prop_str(args, COMMAND_AUDIT_PROP_QUERY_END_DATE);
  const char* str_end_time = tk_object_get_prop_str(args, COMMAND_AUDIT_PROP_QUERY_END_TIME);

  memset(&dt, 0x00, sizeof(dt));
  info->start_date_time = -1;
  info->end_date_time = -1;
  info->username = NULL;
  info->command = NULL;

  if (str_start_time != NULL) {
    date_time_parse_time(&dt, str_start_time);
  }

  if (str_start_date != NULL) {
    date_time_parse_date(&dt, str_start_date);
    info->start_date_time = date_time_to_time(&dt);
  }

  memset(&dt, 0x00, sizeof(dt));
  if (str_end_time != NULL) {
    date_time_parse_time(&dt, str_end_time);
  } else {
    date_time_set_hour(&dt, 23);
    date_time_set_minute(&dt, 59);
    date_time_set_second(&dt, 59);
  }

  if (str_end_date != NULL) {
    date_time_parse_date(&dt, str_end_date);
    info->end_date_time = date_time_to_time(&dt);
  }

  info->command = tk_object_get_prop_str(args, CSV_QUERY_PREFIX "command");
  info->username = tk_object_get_prop_str(args, CSV_QUERY_PREFIX "username");

  return RET_OK;
}

static ret_t command_audit_filter(void* ctx, tk_object_t* args, uint32_t index, csv_row_t* row) {
  command_audit_filter_ctx_t* info = (command_audit_filter_ctx_t*)ctx;

  if (index == 0) {
    command_audit_filter_info_init(info, args);
  }

  if (TK_STR_IS_NOT_EMPTY(info->username)) {
    const char* args = csv_row_get(row, info->username_index);
    if (args == NULL || strstr(args, info->username) == NULL) {
      return RET_FAIL;
    }
  }

  if (TK_STR_IS_NOT_EMPTY(info->command)) {
    const char* command = csv_row_get(row, info->command_index);
    if (command == NULL || strstr(command, info->command) == NULL) {
      return RET_FAIL;
    }
  }

  if (info->end_date_time > 0) {
    const char* date_time = csv_row_get(row, info->time_index);
    int64_t row_date_time = tk_atol(date_time);

    if (row_date_time > info->end_date_time) {
      return RET_FAIL;
    }
  }

  if (info->start_date_time > 0) {
    const char* date_time = csv_row_get(row, info->time_index);
    int64_t row_date_time = tk_atol(date_time);

    if (row_date_time < info->start_date_time) {
      return RET_FAIL;
    }
  }

  if (info->end_date_time > 0 && info->start_date_time > 0) {
    if (info->end_date_time < info->start_date_time) {
      return RET_FAIL;
    }
  }

  return RET_OK;
}

static view_model_t* view_model_command_audit_create(navigator_request_t* req) {
  return csv_view_model_create_ex(req, s_command_audit_vm);
}

static ret_t auto_save_command_audit(const timer_info_t* timer) {
  view_model_t* vm = s_command_audit_vm;
  if (vm != NULL) {
    view_model_exec(vm, TK_OBJECT_CMD_SAVE, NULL);
    return RET_REPEAT;
  } else {
    return RET_REMOVE;
  }
}

ret_t command_audit_init(tk_object_t* model, conf_doc_t* settings) {
  uint32_t field_count = 4;
  uint32_t auto_save_interval = 0;
  uint32_t max_rows = COMMAND_AUDIT_MAX_ROWS;
  char sep = COMMAND_AUDIT_FEIDLS_SEPERATOR;
  const char* col_names = "time|username|command|args";
  command_audit_filter_ctx_t* filter_ctx = &s_command_audit_filter_ctx;

  return_value_if_fail(model != NULL, RET_BAD_PARAMS);
  return_value_if_fail(settings != NULL, RET_BAD_PARAMS);

  if (conf_doc_get_bool(settings, COMMAND_AUDIT_PROP_NAME ".enable", FALSE) == FALSE) {
    log_debug("command_audit is disabled\n");
    return RET_OK;
  }
  auto_save_interval =
      conf_doc_get_int(settings, COMMAND_AUDIT_PROP_NAME ".auto_save_interval", 3000);
  if (auto_save_interval > 0) {
    timer_add(auto_save_command_audit, NULL, auto_save_interval);
  } else {
    timer_add(auto_save_command_audit, NULL, 3000);
  }

  filter_ctx->time_index = 0;
  filter_ctx->username_index = 1;
  filter_ctx->command_index = 2;
  filter_ctx->args_index = 3;

  max_rows = conf_doc_get_int(settings, COMMAND_AUDIT_PROP_NAME ".max_rows", max_rows);
  log_debug("command_audit enabled with(field_count=%u max_rows=%u sep=%c)\n", field_count,
            max_rows, sep);

  s_command_audit_vm = csv_view_model_create(model, COMMAND_AUDIT_PROP_NAME, col_names, field_count, sep,
                                             max_rows, command_audit_filter, filter_ctx);
  view_model_factory_register(COMMAND_AUDIT_PROP_NAME, view_model_command_audit_create);

  if (s_command_audit_vm != NULL) {
    char today[64] = {0};
    date_time_t dt;
    date_time_init(&dt);
    tk_snprintf(today, sizeof(today), "%04d/%02d/%02d", dt.year, dt.month, dt.day);

    tk_object_set_prop_str(TK_OBJECT(s_command_audit_vm), CSV_QUERY_PREFIX "username", "");
    tk_object_set_prop_str(TK_OBJECT(s_command_audit_vm), CSV_QUERY_PREFIX "command", "");
    tk_object_set_prop_str(TK_OBJECT(s_command_audit_vm), COMMAND_AUDIT_PROP_QUERY_END_DATE, today);
  }

  s_default_model = model;
  command_audit_register(model);

  return s_command_audit_vm != NULL ? RET_OK : RET_FAIL;
}

static ret_t command_audit_on_cmd_exec(void* ctx, event_t* e) {
  date_time_t now;
  char message[256] = {0};
  const char* username = tk_object_get_prop_str(s_default_model, "username");
  cmd_exec_event_t* evt = (cmd_exec_event_t*)e;
  tk_object_t* csv = VIEW_MODEL_ARRAY_OBJECT_WRAPPPER(s_command_audit_vm)->obj;
  tk_object_t* obj = TK_OBJECT(e->target);
  return_value_if_fail(csv != NULL, RET_REMOVE);
  return_value_if_fail(obj != NULL, RET_OK);

  date_time_init(&now);
  tk_snprintf(message, sizeof(message)-1, "%lld|%s|%s.%s|%s", date_time_to_time(&now), username,
    obj->name ? obj->name : "", evt->name, evt->args);

  tk_object_exec(csv, TK_OBJECT_CMD_ADD, message);

  return RET_OK;
}

ret_t command_audit_register(tk_object_t* model) {
  return_value_if_fail(model != NULL, RET_BAD_PARAMS);
  if (s_command_audit_vm != NULL) {
    emitter_off_by_ctx(EMITTER(model), s_command_audit_vm);
    emitter_on(EMITTER(model), EVT_CMD_EXECED, command_audit_on_cmd_exec, s_command_audit_vm);

    return RET_OK;
  }

  return RET_FAIL;
}

ret_t command_audit_deinit(void) {
  TK_OBJECT_UNREF(s_command_audit_vm);
  return RET_OK;
}
